﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LibNoise.Utilities {
	/// Defines a color gradient.
    ///
    /// A color gradient is a list of gradually-changing colors.  A color
    /// gradient is defined by a list of <i>gradient points</i>.  Each
    /// gradient point has a position and a color.  In a color gradient, the
    /// colors between two adjacent gradient points are linearly interpolated.
    ///
    /// To add a gradient point to the color gradient, pass its position and
    /// color to the AddGradientPoint() method.
    ///
    /// To retrieve a color from a specific position in the color gradient,
    /// pass that position to the GetColor() method.
    ///
    /// This class is a useful tool for coloring height maps based on
    /// elevation.
    ///
    /// <b>Gradient example</b>
    ///
    /// Suppose a gradient object contains the following gradient points:
    /// - -1.0 maps to black.
    /// - 0.0 maps to white.
    /// - 1.0 maps to red.
    ///
    /// If an application passes -0.5 to the GetColor() method, this method
    /// will return a gray color that is halfway between black and white.
    ///
    /// If an application passes 0.25 to the GetColor() method, this method
    /// will return a very light pink color that is one quarter of the way
    /// between white and red.
    class GradientColor
    {
		/// Number of gradient points.
        private int m_gradientPointCount;

        /// Array that stores the gradient points.
        private GradientPoint[] m_pGradientPoints;

        /// A color object that is used by a gradient object to store a
        /// temporary value.
        private System.Drawing.Color m_workingColor;

		/// Constructor.
        public GradientColor (){
			m_pGradientPoints = null;
			m_gradientPointCount = 0;
		}

        /// Adds a gradient point to this gradient object.
        ///
        /// @param gradientPos The position of this gradient point.
        /// @param gradientColor The color of this gradient point.
        ///
        /// @pre No two gradient points have the same position.
        ///
        /// @throw noise::ExceptionInvalidParam See the precondition.
        ///
        /// It does not matter which order these gradient points are added.
        public void AddGradientPoint (double gradientPos, System.Drawing.Color gradientColor)
		{
			// Find the insertion point for the new gradient point and insert the new
			// gradient point at that insertion point.  The gradient point array will
			// remain sorted by gradient position.
			int insertionPos = FindInsertionPos(gradientPos);
			InsertAtPos(insertionPos, gradientPos, gradientColor);
		}

        /// Deletes all the gradient points from this gradient object.
        ///
        /// @post All gradient points from this gradient object are deleted.
        public void Clear ()
		{
			m_pGradientPoints = null;
			m_gradientPointCount = 0;
		}

        /// Returns the color at the specified position in the color gradient.
        ///
        /// @param gradientPos The specified position.
        ///
        /// @returns The color at that position.
        public System.Drawing.Color GetColor(double gradientPos)
		{
			if (m_gradientPointCount < 2){
				throw new Exception("Too few gradient points to interpolate");
			}

			// Find the first element in the gradient point array that has a gradient
			// position larger than the gradient position passed to this method.
			int indexPos;
			for (indexPos = 0; indexPos < m_gradientPointCount; indexPos++) {
				if (gradientPos < m_pGradientPoints[indexPos].pos) {
					break;
				}
			}

			// Find the two nearest gradient points so that we can perform linear
			// interpolation on the color.
			int index0 = (int)Utils.Clamp(indexPos - 1, 0, m_gradientPointCount - 1);
			int index1 = (int)Utils.Clamp(indexPos    , 0, m_gradientPointCount - 1);

			// If some gradient points are missing (which occurs if the gradient
			// position passed to this method is greater than the largest gradient
			// position or less than the smallest gradient position in the array), get
			// the corresponding gradient color of the nearest gradient point and exit
			// now.
			if (index0 == index1) {
				m_workingColor = m_pGradientPoints[index1].color;
				return m_workingColor;
			}
  
			// Compute the alpha value used for linear interpolation.
			double input0 = m_pGradientPoints[index0].pos;
			double input1 = m_pGradientPoints[index1].pos;
			double alpha = (gradientPos - input0) / (input1 - input0);

			// Now perform the linear interpolation given the alpha value.
			System.Drawing.Color color0 = m_pGradientPoints[index0].color;
			System.Drawing.Color color1 = m_pGradientPoints[index1].color;
			m_workingColor = System.Drawing.Color.FromArgb(
				(int)Utils.LinearInterp(color0.A, color1.A, alpha),
				(int)Utils.LinearInterp(color0.R, color1.R, alpha),
				(int)Utils.LinearInterp(color0.G, color1.G, alpha),
				(int)Utils.LinearInterp(color0.B, color1.B, alpha));
			return m_workingColor;
		}

        /// Returns a pointer to the array of gradient points in this object.
        ///
        /// @returns A pointer to the array of gradient points.
        ///
        /// Before calling this method, call GetGradientPointCount() to
        /// determine the number of gradient points in this array.
        ///
        /// It is recommended that an application does not store this pointer
        /// for later use since the pointer to the array may change if the
        /// application calls another method of this object.
        public GradientPoint[] GetGradientPointArray()
        {
			return m_pGradientPoints;
        }

        /// Returns the number of gradient points stored in this object.
        ///
        /// @returns The number of gradient points stored in this object.
        public int GetGradientPointCount()
        {
			return m_gradientPointCount;
        }

        /// Determines the array index in which to insert the gradient point
        /// into the internal gradient-point array.
        ///
        /// @param gradientPos The position of this gradient point.
        ///
        /// @returns The array index in which to insert the gradient point.
        ///
        /// @pre No two gradient points have the same input value.
        ///
        /// @throw noise::ExceptionInvalidParam See the precondition.
        ///
        /// By inserting the gradient point at the returned array index, this
        /// object ensures that the gradient-point array is sorted by input
        /// value.  The code that maps a value to a color requires a sorted
        /// gradient-point array.
        private int FindInsertionPos (double gradientPos)
		{
		  int insertionPos;
		  for (insertionPos = 0; insertionPos < m_gradientPointCount;
			insertionPos++) {
			if (gradientPos < m_pGradientPoints[insertionPos].pos) {
			  // We found the array index in which to insert the new gradient point.
			  // Exit now.
			  break;
			} else if (gradientPos == m_pGradientPoints[insertionPos].pos) {
			  // Each gradient point is required to contain a unique gradient
			  // position, so throw an exception.
				throw new Exception("Invalid Parameter");
			}
		  }
		  return insertionPos;
		}

        /// Inserts the gradient point at the specified position in the
        /// internal gradient-point array.
        ///
        /// @param insertionPos The zero-based array position in which to
        /// insert the gradient point.
        /// @param gradientPos The position of this gradient point.
        /// @param gradientColor The color of this gradient point.
        ///
        /// To make room for this new gradient point, this method reallocates
        /// the gradient-point array and shifts all gradient points occurring
        /// after the insertion position up by one.
        ///
        /// Because this object requires that all gradient points in the array
        /// must be sorted by the position, the new gradient point should be
        /// inserted at the position in which the order is still preserved.
        private void InsertAtPos (int insertionPos, double gradientPos, System.Drawing.Color gradientColor){
			// Make room for the new gradient point at the specified insertion position
			// within the gradient point array.  The insertion position is determined by
			// the gradient point's position; the gradient points must be sorted by
			// gradient position within that array.
			GradientPoint[] newGradientPoints;
			newGradientPoints = new GradientPoint[m_gradientPointCount + 1];
			for (int i = 0; i < m_gradientPointCount; i++) {
				if (i < insertionPos) {
					newGradientPoints[i] = m_pGradientPoints[i];
				} else {
					newGradientPoints[i + 1] = m_pGradientPoints[i];
				}
			}
			m_pGradientPoints = newGradientPoints;
			++m_gradientPointCount;

			// Now that we've made room for the new gradient point within the array, add
			// the new gradient point.
			m_pGradientPoints[insertionPos].pos = gradientPos ;
			m_pGradientPoints[insertionPos].color = gradientColor;
		}
    };
}
